package de.ttf.ts.control.impl;

import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;

import javax.persistence.EntityManager;
import javax.persistence.OptimisticLockException;
import javax.persistence.PersistenceContext;

import de.ttf.ts.control.exception.ServiceException;
import de.ttf.ts.control.impl.dao.BaseDao;
import de.ttf.ts.model.BaseEntity;
import de.ttf.ts.model.EntityInfo;
import de.ttf.ts.shared.PersistenceLifecycleAware;

public abstract class AbstractController implements PersistenceLifecycleAware {

    @PersistenceContext
    private EntityManager em;

    private final ThreadLocal<Collection<Runnable>> toBeSaved = new ThreadLocal<Collection<Runnable>>() {
        @Override
        protected Collection<Runnable> initialValue() {
            return new HashSet<Runnable>();
        }
    };

    @Override
    public void after(boolean success) {
        try {
            if (success) {
                for (Runnable toSave : toBeSaved.get()) {
                    toSave.run();
                }
            }
        } finally {
            toBeSaved.remove();
        }
    }

    protected <T extends BaseEntity<T>> Iterable<T> find(BaseDao<T> dao, Collection<EntityInfo<T>> infos) {
       
    	Collection<Long> ids = getIds(infos);
        if (ids.isEmpty()) {
            return Collections.emptySet();
        }
        Iterable<T> all = dao.findAll(ids);
        for (T e : all) {
            assertUnmodified(getInfo(infos, e.getId()), e);
        }
        return all;
    }

  
    protected <T extends BaseEntity<T>> T find(BaseDao<T> dao, EntityInfo<T> info) {
    	if(info == null) {
    		return null;
    	}
        Iterator<T> iterator = find(dao, Collections.singleton(info)).iterator();
        return iterator.hasNext() ? iterator.next() : null;
    }

    protected <T extends BaseEntity<T>, R extends T> R findOrCreate(Class<R> type, BaseDao<T> dao, EntityInfo<T> info) {
        if (info == null) {
            return create(dao, type);
        } else {
            return type.cast(find(dao, info));
        }
    }

    protected <T extends BaseEntity<T>, R extends T> R create(final BaseDao<T> dao, Class<R> type) {
        try {
            final R e = type.newInstance();
            toBeSaved.get().add(new Runnable() {
                @Override
                public void run() {
                    dao.save(e);
                }
            });
            return e;
        } catch (InstantiationException e) {
            throw new ServiceException(e);
        } catch (IllegalAccessException e) {
            throw new ServiceException(e);
        }
    }

    protected <T extends BaseEntity<T>> void assertUnmodified(EntityInfo<T> info, T e) {
        if (info.getId() != e.getId()) {
            throw new IllegalArgumentException(String.format("%s does not match %s", info, e));
        }
        if (!e.isVersion(info.getVersion())) {
            throw new OptimisticLockException(e);
        }
    }

    protected <T extends BaseEntity<T>> Collection<Long> getIds(Collection<EntityInfo<T>> infos) {
        Collection<Long> ids = new HashSet<Long>();
        for (EntityInfo<?> info : infos) {
            if (info != null) {
                ids.add(info.getId());
            }
        }
        return ids;
    }

    protected <T extends BaseEntity<T>> EntityInfo<T> getInfo(Collection<EntityInfo<T>> infos, long id) {
        for (EntityInfo<T> info : infos) {
            if (info.getId() == id) {
                return info;
            }
        }
        return null;
    }

    protected <T extends BaseEntity<T>> void setCollection(Collection<T> collection, BaseDao<T> dao,
            Collection<EntityInfo<T>> newInfos) {
        if (newInfos != null) {
            collection.clear();
            for (EntityInfo<T> newInfo : newInfos) {
                collection.add(find(dao, newInfo));
            }
        }
    }

    protected <T> void setCollection(Collection<T> collection, Collection<T> newValues) {
        if (newValues != null) {
            collection.clear();
            collection.addAll(newValues);
        }
    }

    protected <T extends BaseEntity<T>> void modifyCollection(Collection<T> collection, BaseDao<T> dao,
            Collection<EntityInfo<T>> add, Collection<EntityInfo<T>> remove) {
        if (remove != null) {
            for (EntityInfo<T> r : remove) {
                collection.remove(find(dao, r));
            }
        }
        if (add != null) {
            for (EntityInfo<T> a : add) {
                collection.add(find(dao, a));
            }
        }
    }

    protected <T> void modifyCollection(Collection<T> collection, Collection<T> add, Collection<T> remove) {
        if (remove != null) {
            collection.removeAll(remove);
        }
        if (add != null) {
            collection.addAll(add);
        }
    }

}
